/**
 * License would add here
 */

#ifndef UTILS_JS_CONSTRUCTOR_H
#define UTILS_JS_CONSTRUCTOR_H

#include <node_api.h>

#include <csignal>
#include <cstring>

#include "js_native_api.h"
#include "js_native_api_types.h"
#include "js_object.h"
#include "js_value_base.h"
#include "macros.h"

#define IS_FUNCTION(type) VALUE_TYPE_CHECK(IsFunction(), "function", (type))

class JSContructor : public JSObject {
public:
    JSContructor(const JSString& name,
        napi_callback constructor,
        size_t propertyCount = 0,
        napi_property_descriptor* properties = nullptr,
        void* data = nullptr)
        : JSObject(GetEnv(name), true)
    {
        NAPI_STATUS_CALL(env_, napi_define_class(env_, name.GetValue().c_str(), name.Length(), constructor, data, propertyCount, properties, &value_));
        UpdateType();
    }
    JSContructor(const napi_env& env,
        const napi_value& name,
        napi_callback constructor,
        size_t propertyCount = 0,
        napi_property_descriptor* properties = nullptr,
        void* data = nullptr)
        : JSObject(env, true)
    {
        JSString jsName(env, name);
        NAPI_STATUS_CALL(env_, napi_define_class(env, jsName.GetValue().c_str(), jsName.Length(), constructor, data, propertyCount, properties, &value_));
        UpdateType();
    }
    JSContructor(const napi_env& env,
        const std::string& name,
        napi_callback constructor,
        size_t propertyCount = 0,
        napi_property_descriptor* properties = nullptr,
        void* data = nullptr)
        : JSObject(env, true)
    {
        NAPI_STATUS_CALL(env_, napi_define_class(env, name.c_str(), name.length(), constructor, data, propertyCount, properties, &value_));
        UpdateType();
    }
    JSContructor(const napi_env& env, const napi_value& constructor)
        : JSObject(env, constructor)
    {
        IS_FUNCTION(type_);
    }
    JSContructor(const JSValue& value)
        : JSObject(value)
    {
        IS_FUNCTION(type_);
    };
    JSContructor(const JSContructor& value)
        : JSObject(value) {};

    inline JSObject New()
    {
        JSValue result(env_);
        NAPI_STATUS_CALL(env_, napi_new_instance(env_, value_, 0, nullptr, &result));
        result.UpdateType();
        return result.As<JSObject>();
    }

    inline JSObject New(std::initializer_list<JSValue> args)
    {
        size_t argc = args.size();

        napi_value* argv = new napi_value[argc];
        auto iter = args.begin();
        for (size_t i = 0; i < args.size(); i++) {
            argv[i] = *iter;
            iter++;
        }

        JSValue result(env_);
        NAPI_STATUS_CALL(env_, napi_new_instance(env_, value_, argc, argv, &result));
        result.UpdateType();
        delete[] argv;
        return result.As<JSObject>();
    }
    inline JSObject New(std::initializer_list<napi_value> args)
    {
        size_t argc = args.size();

        napi_value* argv = new napi_value[argc];
        auto iter = args.begin();
        for (size_t i = 0; i < args.size(); i++) {
            argv[i] = *iter;
            iter++;
        }

        JSValue result(env_);
        NAPI_STATUS_CALL(env_, napi_new_instance(env_, value_, argc, argv, &result));
        result.UpdateType();
        delete[] argv;

        return result.As<JSObject>();
    }
    inline JSObject New(size_t argc, JSValue* args)
    {
        napi_value* argv = argc > 0 ? new napi_value[argc] : nullptr;
        for (size_t i = 0; i < argc; i++) {
            argv[i] = args[i];
        }

        JSValue result(env_);
        NAPI_STATUS_CALL(env_, napi_new_instance(env_, value_, argc, argv, &result));
        result.UpdateType();
        delete[] argv;

        return result.As<JSObject>();
    }
    inline JSObject New(size_t argc, napi_value* args)
    {

        JSValue result(env_);
        NAPI_STATUS_CALL(env_, napi_new_instance(env_, value_, argc, args, &result));
        result.UpdateType();

        return result.As<JSObject>();
    }
    inline JSObject operator()() { return New(); }
    inline JSObject operator()(std::initializer_list<JSValue> args)
    {
        return New(args);
    }
    inline JSObject operator()(std::initializer_list<napi_value> args)
    {
        return New(args);
    }
    inline JSObject operator()(size_t argc, JSValue* args)
    {
        return New(argc, args);
    }
    inline JSObject operator()(size_t argc, napi_value* args)
    {
        return New(argc, args);
    }

    inline JSString Name() { return Get("name").As<JSString>(); }

private:
};

#undef IS_FUNCTION
#endif /* UTILS_JS_CONSTRUCTOR_H */
